#Pyton library imports
from itertools import product
import re
#External libraries imports
from pyparsing import *
from numpy import array
#Internal imports
from Exceptions import SemanticException, LabelsMismatchException
import FSPScopeHandling as scope
import FSPProcess as proc
from ParsingUtilities import *
from FSPSymbolParser import FSPSymbol

lowercaseRegex = "[a-z][a-zA-Z0-9]*"

uppercaseRegex = "[A-Z][a-zA-Z0-9]*"

LowercaseIdentifier = Regex(lowercaseRegex)

UppercaseIdentifier = Regex(uppercaseRegex)

ConstantIdentifier = FSPSymbol(uppercaseRegex, "const", scope.globalNamespace)

RangeIdentifier = FSPSymbol(uppercaseRegex, "range", scope.globalNamespace)

SetIdentifier = FSPSymbol(uppercaseRegex, "set", scope.globalNamespace).setParseAction(lambda x: [x.asList()])

ParameterIdentifier = UppercaseIdentifier

#TESTIAMO LE THEANO EXPRESSIONS! from FSPExpressionParser import Expression
from FSPExpressionParser2 import Expression, variableRegex

ConstantDefinition =\
  (
    Suppress(Keyword("const")).setParseAction(scope.setGlobalNamespace) +
    UppercaseIdentifier +
    lit("=") +
    Expression #da qui deve uscire un int
  ).setParseAction(lambda s, l, t: scope.currentNamespace.addObject(s, l, t[0],
                                                                    int(t[1]),
                                                                    "const"))

def validateRangeLimits(s, l, t):
  beginning = 0
  end = 0
  try:
    beginning = eval(t[0])
    end = eval(t[1])
    if beginning > end: raise Exception
  except:
    raise SemanticException(s, l, "Bad syntax for range expression.")
  return [range(beginning, end)]

RangeDefinition =\
  (
    Suppress(Keyword("range")).setParseAction(scope.setGlobalNamespace) +
    UppercaseIdentifier +
    lit("=") +
    Expression + lit("..") + Expression #devono uscire due int
  ).setParseAction\
  (
    lambda s,l,t: [t[0]] + validateRangeLimits(s,l,t[1:]),
    lambda s, l, t:
      scope.currentNamespace.addObject(s, l, t[0], t[1], "range")
  )

"""problem: what if one of the limits is an expression depending on a variable?
If so, the rangeExpression remains literal and is seen as a set of 1 label
(the range expression itself), so it can safely pass the preprocessing phase.
In the variable resolution phase, these unresolved expressions must be resolved."""
def manageRangeExpression(s, l, t):
  if re.match(variableRegex, t[0]) or re.match(variableRegex, t[1]):
    return {t[0]+".."+t[1]}
  return validateRangeLimits(s, l, t)[0]

RangeExpression =\
  (
    (Expression + lit("..") + Expression) #devono uscire due int
    .setParseAction(manageRangeExpression)
  )

Range = RangeIdentifier ^ RangeExpression

ActionLabels = Forward()

def uniquify(x):
  seen = {}
  result = []
  for item in x:
    if item in seen: continue
    seen[item] = 1
    result.append(item)
  return result

SetElements =\
  (
    ActionLabels + ZeroOrMore(lit(",") + ActionLabels)
  ).setParseAction(lambda l: [uniquify(l.asList())])

SetExpression =\
  (lit("{") + SetElements + lit("}"))

SetDefinition =\
  (
    Suppress(Keyword("set")).setParseAction(scope.setGlobalNamespace) +
    UppercaseIdentifier +
    lit("=") +
    SetExpression
  ).setParseAction\
  (lambda s, l, t: scope.currentNamespace.addObject(s, l, t[0], t[1], "set"))

Set = SetIdentifier ^ SetExpression

ActionLabel =\
  (
    Combine
    (
      (LowercaseIdentifier ^ (Literal("[") + Expression + Literal("]"))) +
      ZeroOrMore(
        (
          (Literal(".") + LowercaseIdentifier)
          ^
          (Literal("[") + Expression + Literal("]"))
        )
      )
    )
  )

def expandActionLabels(components):
  components = components.asList()
  components = [[c] if type(c) == str else list(c) for c in components]
  strComponents = []
  for s in components:
    for e in s:
      if type(e) != str:
        s = [str(i) if i == e else e]
    strComponents.append(s)
  strComponents = [''.join(e) for e in product(*strComponents)]
  return strComponents

def pyListReprToFSPSetRepr(x):
  return "{" + str(x)[1:-1].replace("'", "") + "}"

VariableDefinition = (
  LowercaseIdentifier + Literal(":") +
  (
    UppercaseIdentifier ^ #set or range identifiers
    (Expression + Literal("..") + Expression)^ #range expression
    SetExpression.copy().setParseAction(pyListReprToFSPSetRepr)
  )).setParseAction(lambda x: [[strJoin(x)]])

ActionRange =\
  (
    SetIdentifier
    ^SetExpression
    ^RangeIdentifier.copy().addParseAction(lambda t: [[str(i) for i in t[0]]])
    ^RangeExpression.copy().addParseAction(lambda t: [[str(i) for i in t[0]]])
    ^VariableDefinition
    # (Variable >> lit(":") >> Set)   |
  )

ActionLabels <<\
  (
    (ActionLabel ^ Set ^ (Literal("[") + ActionRange + Literal("]"))) +
    ZeroOrMore
    (
      (Literal(".") + ActionLabel) ^
      (Literal(".") + Set) ^
      (Literal("[") + ActionRange + Literal("]")) ^
      Combine(Literal("[") + Expression + Literal("]"))
    )
  ).setParseAction(expandActionLabels)

PrefixActions =\
  (Group(ActionLabels) + ZeroOrMore(Literal("->") + Group(ActionLabels))).setParseAction(
    expandActionLabels
  )

Guard = Group(Keyword("when") + Expression)

LocalProcess = Forward()

def insertChoiceSymbol(x):
  print("prefix actions da chainare: ", x)
  i = 0
  while i != len(x) - 1:
    x.insert(i + 1, "|")
    i = i+2
  return x

def expandActions(x):
  returnList = []
  if x[0][0] == "when":
    if not re.match(variableRegex, x[0][1]):
      if eval(x[0][1]) == 0:
        return [""]
      else:
        for action in x[1]:
          returnList = returnList + [action] + ["->"] + [x[3]] + ["|"]
    else:
      for action in x[1]:
        returnList = returnList + ["when "] + [x[0][1]] + [" ", action] + ["->"] + [x[3]] + ["|"]
        print("La mi' roba:", returnList)
  else:
      for action in x[0]:
        returnList = returnList + [action] + ["->"] + [x[2]] + ["|"]
  if returnList[-1] == "|": del returnList[-1]
  return strJoin(returnList)

ActionPrefix =\
  (Optional(Guard) + Group(PrefixActions) + Literal("->") + LocalProcess).setParseAction(expandActions)

Indices = OneOrMore(Literal("[") + Expression + Literal("]"))

IndexRanges = OneOrMore(
  (Literal("[") + Expression + Literal("]")).setParseAction(strJoin) ^
  (lit("[") + ActionRange + lit("]")).setParseAction(
    lambda x: ["[" + string + "]" for actRange in x for string in actRange]))

ParameterDefinition = ParameterIdentifier + Literal("=") + Expression

Parameters = (Literal("(") +
  ParameterDefinition + ZeroOrMore(lit(",") + ParameterDefinition) +
  Literal(")"))

BaseLocalProcess =\
  (
    Literal("END") ^ Literal("STOP") ^ Literal("ERROR") ^
    (UppercaseIdentifier + Optional(Indices))
  )

def addBars(x):
  x = x.asList()
  i = 0
  while i != len(x) - 1:
    if x[i] != "":
      x.insert(i+1, "|")
      i = i+2
    else: i = i+1
  return x

Choice = (ActionPrefix + ZeroOrMore(lit("|") + ActionPrefix)).setParseAction(addBars, strJoin)

LocalProcess <<\
  (
    BaseLocalProcess ^
    #SequentialComposition ^
    #if Expression then LocalProcess ^
    #if Expression then LocalProcess else LocalProcess ^
    (Literal("(") + Choice + Literal(")"))
  ).setParseAction(strJoin)

def manageIndexRanges(x):
  returnList = []
  if x[1] != "=":
    for indexRange in x[1].asList():
      returnList = returnList + [x[0], indexRange, "=", x[3], ","]
    if returnList[-1] == ",": del returnList[-1]
  else:
    returnList = returnList + [x[0], "=", x[2]]
  return strJoin(returnList)

LocalProcessDefinition =\
  (
    UppercaseIdentifier + Optional(Group(IndexRanges)) + Literal("=") + LocalProcess
  ).setParseAction(manageIndexRanges)

ProcessBody =\
  (
    LocalProcess + ZeroOrMore(Literal(",") + LocalProcessDefinition)
  )

AlphabetExtension = (Literal("+") + Set).setParseAction(lambda x: [x[0], pyListReprToFSPSetRepr(x[1])])

def relabelToString(s, l, x):
  print("relabelToString: x è: ", x)
  if len(x[0]) != len(x[1]): raise LabelsMismatchException(s, l)
  string = ""
  for i in range(0, len(x[0])):
    string = string + x[0][i] + "/" + x[1][i] + ("," if i != len(x[0]) - 1 else "")
  return string

RelabelRule =\
  (
    Group(ActionLabels) +
    Suppress(Literal("/")) +
    Group(ActionLabels)
  ).setParseAction(lambda s,l,t: relabelToString(s,l,t))
#oppure forall IndexRanges { RelabelDefs }

RelabelRules = RelabelRule + ZeroOrMore(Literal(",") + RelabelRule)

Relabel = Literal("/") + Literal("{") + RelabelRules + Literal("}")

SelectiveExposition = (Literal("@") + Set).setParseAction(lambda x: [x[0], pyListReprToFSPSetRepr(x[1])])

SelectiveHiding = (Literal("\\") + Set).setParseAction(lambda x: [x[0], pyListReprToFSPSetRepr(x[1])])

Hiding = SelectiveHiding ^ SelectiveExposition

processes = []

def push(x):
  processes.append(strJoin(x))
  return x

ProcessDefinition =\
  (
    UppercaseIdentifier + Optional(Parameters) + Literal("=") +
    ProcessBody +
    Optional(AlphabetExtension) +
    Optional(Relabel) +
    Optional(Hiding) +
    Literal(".")
  ).setParseAction(push)

Test = ZeroOrMore(ConstantDefinition|RangeDefinition|SetDefinition|ProcessDefinition)
